#include "LBTR.h"
#include "WSNNotifierConsts.h"
#include "IPv4Datagram.h"
#include "IPv4Address.h"
#include "IPv4Route.h"
#include "IPv4ControlInfo.h"
#include "WSNIPProtocolld.h"
#include "WSNNotifierConsts.h"
#include "InterfaceTableAccess.h"
#include "InterfaceEntry.h"
#include "IPv4InterfaceData.h"
#include "LBTRPacket_m.h"
#include "BasicBattery.h"
#include "RoutingTableAccess.h"
#include "NotificationBoard.h"
#include <limits>
#include <list>

#define MAX_DEPTH 10000

#define ENERGY_THRESHOLD 5
#define CONTROL_METRIC 100000

// Timer (in seconds)
#define WAIT_BEACON_ACK par("beaconACKTimeout")
#define SEND_BEACON_REQ par("beaconREQTimer")

// Max value of metric is 2,147,483,647
#define MAX_METRIC std::numeric_limits<int>::max()


Define_Module(LBTR);

LBTR :: LBTR() {

    depth = MAX_DEPTH;
    baseStationAddr = IPv4Address::UNSPECIFIED_ADDRESS;
    isUp = true;

    // Generate timer
    maintenanceTimer = new cMessage("BEACON_REQ");
    maintenanceTimer->setKind(LBTR_BEACON_REQ);
    ackTimeoutTimer = new cMessage("ACK_TIMEOUT");
    ackTimeoutTimer->setKind(LBTR_BEACON_ACK);

}

LBTR :: ~LBTR() {

}

void LBTR :: initialize(int stage) {

    if(stage == 0){

        WATCH(depth);
        WATCH(baseStationAddr);
    }
    if (stage == 4){
        nb = NotificationBoardAccess().get();
        irt = RoutingTableAccess().get();
        ift = InterfaceTableAccess().get();
        bat = BatteryAccess().getIfExists();
        ie = ift->getInterfaceByName("wlan0");

        // Listening on battery exhausted
        nb->subscribe(this, BATTERY_RESIDUAL_EMPTY);

        irt->deleteRoute(irt->getRoute(0));
        irt->deleteRoute(irt->getRoute(0));

        if(par("isBaseStation").boolValue()==true)
        {
            // If this is base station,
            // set its depth = 0
            // set baseStationAddr = itself address
            depth = 0;
            baseStationAddr = ie->ipv4Data()->getIPAddress();

            //Create a setup Tree packet and send it to all neighbor hop
            EV << "LBTR: Creating a setup tree packet..." << endl;
            LBTRPacket *pkt = createSetupPacket();

            sendOut(pkt, IPv4Address::ALL_HOSTS_MCAST, "setupTREE");
        }
        else {

            startMaintenanceTimer();
        }

    }
}

/*
 * @Author Hien Hoang
 * @Version Nov 29, 2013
 * @Description
 * . The handleMessage work as a message dispatcher to dispatch two type of
 * . message. One is timer message, we will send it to handleSelfMessage.
 * . and second is LBTR protocol packet, with two kind is setup packet, and
 * . the BEACON packet. With setupPacket we'll let the handleSetupPacket process
 * . it, and with the beaconPacket we'll let the handleBeaconPacket process it.
 * */
void LBTR :: handleMessage(cMessage *msg) {

    if (isUp == false) {
        delete msg;
        return;
    }

    if (msg->isSelfMessage()) {

        handleSelfMessage(msg);
    }
    else {
        EV << "LBTR: Received packet, processing..." << endl;
        IPv4Datagram *dgram = check_and_cast<IPv4Datagram *>(msg);
        LBTRPacket *pkt = dynamic_cast<LBTRPacket *>(dgram->decapsulate());
        IPv4Address &parentAddr = dgram->getSrcAddress();
        if (pkt->getKind() == LBTR_SETUP) {
            LBTRSetupPacket *setPkt = dynamic_cast<LBTRSetupPacket *>(pkt);
            handleSetupPacket(setPkt, parentAddr);
        }
        if (pkt->getKind() == LBTR_BEACON_REQ || pkt->getKind() == LBTR_BEACON_ACK) {
            LBTRBeaconPacket *beaPkt = dynamic_cast<LBTRBeaconPacket *>(pkt);
            handleBeaconPacket(beaPkt, parentAddr);
        }
         delete msg;
    }
}

/*
 * @Author Hien Hoang
 * @Version Dec 2, 2013
 * @Description
 * .     The handleSelfMessage target to process the timer that use for
 * . maintenance phase like repeatedly send the BEACON_REQ in a specified
 * . time, or the ACK timeout.
 * .     If the message kind is LBTR_BEACON_REQ then execute the send BEACON_REQ
 * . phase and then repeat this phase in future few second by set the scheduleAt
 * . and also start the wait ACK timer. Notes: the waitACK timer must lower than
 * . the SEND BEACON REQ timer
 * */
void LBTR :: handleSelfMessage(cMessage *msg) {

    if (msg->getKind() == LBTR_BEACON_REQ) {

        // Send request here
        EV << "[LBTR]! Hey, I'm " << ie->ipv4Data()->getIPAddress() << endl;
        EV << "[LBTR]! Time to send BEACON_REQ, sending..." << endl;
        LBTRBeaconPacket *beaPkt = createBeaconPacket();
        sendOut(beaPkt, IPv4Address::ALL_HOSTS_MCAST, "LBTR_BEACON_REQ");

        // Schedule for next maintenance
        scheduleAt(simTime() + SEND_BEACON_REQ, msg);

        // Schedule for ack timeout
        ackTimeoutTimer = new cMessage();
        ackTimeoutTimer->setKind(LBTR_BEACON_ACK);
        scheduleAt(simTime() + WAIT_BEACON_ACK, ackTimeoutTimer);
    }
    else {
        EV << "[LBTR]! Hey, I'm " << ie->ipv4Data()->getIPAddress() << endl;
        EV << "[LBTR]! ACK time out, updating routing table..." << endl;

        /*
         * @Editor Hien Hoang
         * @Date Dec 2, 2013
         * @Description
         * . the following code loop all his neighbor, to check WHO already reply ACK
         * . and with WHO hasn't reply ACK yet, then mark his as died neighbor by set
         * . the gateway metric go to him is MAX_METRIC
         * . {ackList} store WHO already ACK by ackList[IPv4Address] is True
         * . After update neighbor (routing table), we must clear {ackList} for the next
         * . BEACON timer use. (Very important!!!)
         * */
//        std::list<IPv4Route *> deleteList;
        for (int i = 0; i < irt->getNumRoutes(); i++) {
            IPv4Route *route = irt->getRoute(i);

            if ( ackList.find(route->getGateway()) != ackList.end()) {
                if (!ackList[route->getGateway()]) {
                    route->setMetric(MAX_METRIC);
//                    deleteList.push_back(route);
                    EV << route->getGateway() << " sent ack to: " << ie->ipv4Data()->getIPAddress()
                            << " METRIC: " << route->getMetric() << endl;
                }
            }
            else {
                route->setMetric(MAX_METRIC);
//                deleteList.push_back(route);
            }
        }
//        while(deleteList.size() > 0) {
//            IPv4Route *re = deleteList.back();
//            deleteList.pop_back();
//            irt->removeRoute(re);
//        }
        ackList.clear();
        delete msg;
    }

}

/*
 * @Author Hien Hoang
 * @Version Dec 2, 2013
 * @Description
 * .   The handleSetupPacket will process the incoming packet type is LBTRSetupPacket.
 * . This function will work in a first time the BaseStation send the setup packet, and
 * . only in that time. In this setup phase, the function will update the baseStation address,
 * . the depth, the metric from this node to the source and add this source as his neighbor
 * . his can reach.
 * .   The depth will update while my depth is higher than the source depth, and update by
 * . source depth + 1. The metric or the weight for making parent selection will calculate by
 * . calculateMetric function
 * .   After update the information, generate the setup packet with this depth, residual battery
 * . energy, the best metric from him to the base station and multicast this packet to his neighbor
 *
 * */
void LBTR :: handleSetupPacket(LBTRSetupPacket *pkt, IPv4Address parentAddr) {

    // If my base station address is unspecified, let's update it
    if (baseStationAddr.isUnspecified()) {
        baseStationAddr = pkt->getBaseAddr();
        // Notify to LBAPP application
        nb->fireChangeNotification(APPLICATION_BASESTATION_ADDR_ACHIEVE, pkt);
    }

    int metric = MAX_METRIC;

    if (depth > pkt->getDepth() + 1 ) {

        // Stop sending data from application
        nb->fireChangeNotification(APPLICATION_WAIT_SENDING, NULL);
        depth = pkt->getDepth() + 1;
        metric = calculateMetric(pkt->getMetric(), pkt->getDepth(), pkt->getResidualPower());
        updateRoutingTable(baseStationAddr, parentAddr, metric);
        // Send setup packet to my neighbor
        LBTRPacket *newPkt = createSetupPacket();
        sendOut(newPkt, IPv4Address::ALL_HOSTS_MCAST, "setupTREE");
        nb->fireChangeNotification(APPLICATION_CONTINUE_SENDING, NULL);
    }
    else {
        metric = calculateMetric(pkt->getMetric(), pkt->getDepth(), pkt->getResidualPower());
        updateRoutingTable(baseStationAddr, parentAddr, metric);
    }

}

/*
 * @Author Loi Le
 * @Version Dec 1, 2013
 * @Description
 * .hanleBeaconPacket is function when handleMessage() receive BECON message
 * .if received LBTR_BEACON_REQ update packet with his metric, send kind packet is LBTR_BEACON_ACK
 *  send it back
 *  .if receved LBTR_BEACON_ACK using calculateMetric() to calculating metric of source
 *  .update it into routingtable
 * */
void LBTR :: handleBeaconPacket(LBTRBeaconPacket *pkt, IPv4Address srcAddr) {

    switch (pkt->getKind()) {
    case LBTR_BEACON_REQ:
    {
        EV << "[LBTR]! I got a BEACON_REQ from " << srcAddr << ", sending ACK for him...";

        /*
         * @Editor Hien Hoang
         * @Date Dec 2, 2013
         * @Description
         * . Finding a minimum metric gateway that not source
         * . if all gateway metric in routing table is MAX_METRIC
         * . that mean there have no gateway to go out, so the
         * . minMetric will be set to MAX_METRIC
         * */
        int minMetric = MAX_METRIC;
        for (int i = 0; i < irt->getNumRoutes(); i++) {
            IPv4Route *ire = irt->getRoute(i);
            if (ire->getGateway() != srcAddr && ire->getMetric() < minMetric) {
                minMetric = ire->getMetric();
            }
        }
        pkt->setKind(LBTR_BEACON_ACK);
        pkt->setMetric(minMetric);
        pkt->setResidualPower(bat->GetEnergy());
        pkt->setDepth(depth);

        sendOut(pkt, srcAddr, "BECON_ACK");
        break;
    }
    case LBTR_BEACON_ACK:
    {
        EV << "[LBTR]! Hey, i'm " << ie->ipv4Data()->getIPAddress() << endl;
        EV << "[LBTR]! I have already received BEACON_ACK packet from " << srcAddr << endl;
        ackList[srcAddr] = true;
        int metric = calculateMetric(pkt->getMetric(),pkt->getDepth(),pkt->getResidualPower());
        EV << "[LBTR]! I'll update metric: " << metric << " to gateway " << srcAddr << endl;
        updateRoutingTable(baseStationAddr,srcAddr,metric);
        delete pkt;
        break;
    }
    default:
    {
        EV << "[LBTR]! Unknown packet type, deleting... " << endl;
        delete pkt;
        break;
    }
    }
}

/*
 * @Author Hien Hoang
 * @Version Nov 29, 2013
 * @Description
 * . Create a new setup tree packet
 *
 * */
LBTRSetupPacket *LBTR :: createSetupPacket() {

    int metric = 0;
    if(par("isBaseStation").boolValue()==false) {
        IPv4Route *rt = irt->findBestMatchingRoute(baseStationAddr);
        metric = rt->getMetric();
    }
    EV << "MY METRIC IS: " << metric << endl;
    LBTRSetupPacket *setPkt = new LBTRSetupPacket("setupTREE");
    setPkt->setKind(LBTR_SETUP);
    setPkt->setDepth(depth);
    setPkt->setBaseAddr(baseStationAddr);
    setPkt->setResidualPower(bat->GetEnergy());
    setPkt->setMetric(metric);

    return setPkt;
}

LBTRBeaconPacket *LBTR :: createBeaconPacket() {

    LBTRBeaconPacket *beaPkt = new LBTRBeaconPacket("BEACON_REQ");
    beaPkt->setKind(LBTR_BEACON_REQ);

    return beaPkt;
}

/*
 * @Author Hien Hoang
 * @Version Nov 29, 2013
 * @Description
 * . Create a new IPv4 Datagram
 * */
IPv4Datagram *LBTR :: createIPv4Datagram(LBTRPacket *pkt, IPv4Address dest, char *name) {

    IPv4Datagram *datagram = new IPv4Datagram(name);

    datagram->setDestAddress(dest);
    datagram->setTransportProtocol(IP_PROT_LBTR);
    datagram->setTimeToLive(15);
    datagram->encapsulate(pkt);

    return datagram;
}

/*
 * @Author Hien Hoang
 * @Verison Dec 2, 2013
 * @Description
 * . The updateRouting table will update the routing table (neighbor gateway metric)
 * */
void LBTR :: updateRoutingTable(IPv4Address destAddr, IPv4Address gatewayAddr, int metric) {

    bool isNew = true;
    IPv4Route *route;
    for (int i = 0; i < irt->getNumRoutes(); i++) {
        route = irt->getRoute(i);
        if ( route->getDestination() == destAddr.getNetwork() && route->getGateway() == gatewayAddr) {
            isNew = false;
            EV << "[LBTR]! Yeah! I found gateway " << route->getGateway() << " change metric from " << route->getMetric()
                    << " to " << metric;
            route->setMetric(metric);
            break;
        }
    }
    if (isNew) {

        EV << "Sorry, I can't find this gateway, adding new..." << endl;
        route = new IPv4Route();
        route->setDestination(baseStationAddr.getNetwork());
        route->setGateway(gatewayAddr);
        route->setNetmask(IPv4Address(255,255,255,0));
        route->setInterface(ie);
        route->setSource(IPv4Route::MANUAL);
        route->setMetric(metric);
        irt->addRoute(route);
    }
}

/*
 * @Author Hien Hoang
 * @Version Dec 2, 2013
 * @Description
 * . The calculateMetric algorithm is very important for making parent selection
 * . to send data go home (Base station)
 * . The lower of metric beside the lower of the depth and the higher of the residualBattery
 * */
int LBTR :: calculateMetric(int parentMetric, int depth, double residualBattery) {

    int metric = 0;
    if (residualBattery < ENERGY_THRESHOLD || parentMetric == MAX_METRIC) {

        metric = MAX_METRIC;
        // EV << "BATTERY TO LOW, ResidualBattery:  "<< residualBattery << endl;
    }
    else if (parentMetric >= MAX_METRIC - ((depth * CONTROL_METRIC) + (CONTROL_METRIC / (int)residualBattery))){

        metric = MAX_METRIC;
    }
    else {

        metric = parentMetric + (depth * CONTROL_METRIC) + (CONTROL_METRIC / (int)residualBattery);
        // EV << "METRIC AFTER CALCULATE: " << metric << endl;
    }

    return metric;
}

void LBTR :: sendOut(LBTRPacket *pkt, IPv4Address dest, char *name) {

    IPv4Datagram *datagram = createIPv4Datagram(pkt, dest, name);

    send(datagram, "ipOut");
}

void LBTR :: startMaintenanceTimer() {

    scheduleAt(simTime() + SEND_BEACON_REQ, maintenanceTimer);
}

void LBTR :: stopMaintenanceTimer() {

    if (maintenanceTimer != NULL)
    {
        cancelEvent(maintenanceTimer);
        delete maintenanceTimer;
    }
    if (ackTimeoutTimer != NULL)
    {
        cancelEvent(ackTimeoutTimer);
        delete ackTimeoutTimer;
    }
}

void LBTR :: receiveChangeNotification(int category, const cObject *details) {

    if (category == BATTERY_RESIDUAL_EMPTY && isUp == true) {

        EV << "Battery exhausted, LBTR going down..." << endl;
        isUp = false;
        stopMaintenanceTimer();
    }
}


